using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Sql;

namespace Eve.Data
{
    public class DbObject
    {
        public static DbObject NullObject = new DbObject(null);

        private object value;

        public bool IsNull
        {
            get { return value == null; }
        }
        public static implicit operator byte(DbObject v)
        {
            if (v.IsNull) return 0;
            return byte.Parse(v.value.ToString());
        }
        public static implicit operator sbyte(DbObject v)
        {
            if (v.IsNull) return 0;
            return sbyte.Parse(v.value.ToString());
        }
        public static implicit operator char(DbObject v)
        {
            if (v.value == null) return (char)0;
            return v.value.ToString()[0];
        }
        public static implicit operator short(DbObject v)
        {
            if (v.IsNull) return 0;
            return short.Parse(v.value.ToString());
        }
        public static implicit operator ushort(DbObject v)
        {
            if (v.IsNull) return 0;
            return ushort.Parse(v.value.ToString());
        }
        public static implicit operator int(DbObject v)
        {
            if (v.value == null) return 0;
            return int.Parse(v.value.ToString());
        }
        public static implicit operator uint(DbObject v)
        {
            if (v.IsNull) return 0;
            return uint.Parse(v.value.ToString());
        }
        public static implicit operator long(DbObject v)
        {
            if (v.value == null) return 0;
            return long.Parse(v.value.ToString());
        }
        public static implicit operator ulong(DbObject v)
        {
            if (v.value == null) return 0;
            return ulong.Parse(v.value.ToString());
        }
        public static implicit operator float(DbObject v)
        {
            if (v.value == null) return 0;
            return float.Parse(v.value.ToString());
        }
        public static implicit operator double(DbObject v)
        {
            if (v.value == null) return 0.0;
            return double.Parse(v.value.ToString());
        }
        public static implicit operator string(DbObject v)
        {
            if (v.value == null) return "";
            return v.value.ToString();
        }
        public static implicit operator bool(DbObject v)
        {
            return (int)v != 0;
        }
        public static implicit operator DateTime(DbObject v)
        {
            if (v.value == null) return DateTime.MinValue;
            return DateTime.Parse(v.value.ToString());
        }

        public DbObject(object val)
        {
            this.value = val;
        }

        public static bool operator !=(DbObject a, DbObject b)
        {
            if (ReferenceEquals(a, null) && ReferenceEquals(b, null)) return false;
            else if (ReferenceEquals(a, null) && b.IsNull) return false;
            else if (ReferenceEquals(b, null) && a.IsNull) return false;
            else if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return true;
            else return !a.Equals(b);
        }
        public static bool operator ==(DbObject a, DbObject b)
        {
            if (ReferenceEquals(a, null) && ReferenceEquals(b, null)) return true;
            else if (ReferenceEquals(a, null) && b.IsNull) return true;
            else if (ReferenceEquals(b, null) && a.IsNull) return true;
            else if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
            else return a.Equals(b);
        }

        public bool Equals(DbObject obj)
        {
            if (obj == null) return IsNull;
            else return object.Equals(obj.value, value);
        }
        public override bool Equals(object obj)
        {
            return object.Equals(value, obj);
        }
        public override int GetHashCode()
        {
            if (value == null) return 0;
            return value.GetHashCode();
        }
        public override string ToString()
        {
            if (value == null) return "";
            return value.ToString();
        }
    }
    public class DbDataColumn
    {
        private Database db;
        private DataColumn dc;
        private DbObject defaultValue;

        public string ColumnName
        {
            get { return dc.ColumnName; }
        }
        public System.Type DataType
        {
            get { return dc.DataType; }
        }
        public int MaxLength
        {
            get { return dc.MaxLength; }
        }
        public DbObject DefaultValue
        {
            get { return defaultValue; }
        }
        public bool AllowNULL
        {
            get { return dc.AllowDBNull; }
        }
        internal Database Database
        {
            get { return db; }
        }

        internal DbDataColumn(Database db, DataColumn dc)
        {
            this.db = db;
            this.dc = dc;
            this.defaultValue = new DbObject(dc.DefaultValue);
        }
    }
    public class DbDataColumnCollection : IEnumerable<DbDataColumn>
    {
        private Database db;
        private List<DbDataColumn> collection = new List<DbDataColumn>();
        private Dictionary<string, DbDataColumn> names = new Dictionary<string,DbDataColumn>();

        public DbDataColumn this[int index]
        {
            get { return collection[index]; }
        }
        public DbDataColumn this[string column]
        {
            get { return names[column]; }
        }
        public int Count
        {
            get { return collection.Count; }
        }
        public bool Contains(string column)
        {
            return names.ContainsKey(column);
        }

        public void CopyTo(DbDataColumn[] array, int arrayIndex)
        {
            collection.CopyTo(array, arrayIndex);
        }

        internal DbDataColumnCollection(Database db, DataColumnCollection dcc)
        {
            this.db = db;
            foreach (DataColumn dc in dcc)
            {
                DbDataColumn ddc = new DbDataColumn(db, dc);
                collection.Add(ddc);
                names[dc.ColumnName] = ddc;
            }
        }

        internal void Add(DbDataColumn item)
        {
            collection.Add(item);
        }
        internal void Clear()
        {
            collection.Clear();
        }
        internal bool Remove(DbDataColumn item)
        {
            return collection.Remove(item);
        }

        #region IEnumerable<DbDataColumn> Members
        public IEnumerator<DbDataColumn> GetEnumerator()
        {
            return collection.GetEnumerator();
        }
        #endregion
        #region IEnumerable Members
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion
    }
    public class DbDataRow
    {
        private Database db;
        private DataRow dr;
        private DbDataColumnCollection columns;
        private List<DbObject> values = new List<DbObject>();
        private Dictionary<string, DbObject> namedValues = new Dictionary<string, DbObject>();

        public DbObject this[int index]
        {
            get 
            { 
                if (index < values.Count) return values[index];
                return DbObject.NullObject;
            }
        }
        public DbObject this[string column]
        {
            get 
            { 
                if (namedValues.ContainsKey(column)) return namedValues[column];
                return DbObject.NullObject;
            }
        }
        public DbDataColumnCollection Columns
        {
            get { return columns;  }
        }
        internal Database Database
        {
            get { return db; }
        }

        internal DbDataRow(Database db, DataRow dr, DataColumnCollection dcc)
        {
            this.db = db;
            this.dr = dr;

            columns = new DbDataColumnCollection(db, dcc);
            for (int i = 0; i < columns.Count; i++)
            {
                DbObject obj = new DbObject(dr.IsNull(i) ? null : dr[i]);
                values.Add(obj);
                namedValues[columns[i].ColumnName] = obj;
            }
        }
    }
    public class DbDataRowCollection : IEnumerable<DbDataRow>
    {
        private Database db;
        private List<DbDataRow> collection = new List<DbDataRow>();
        private DbDataColumnCollection columns;

        public DbDataRow this[int index]
        {
            get { return collection[index]; }
        }
        public DbDataColumnCollection Columns
        {
            get { return columns; }
        }
        public int Count
        {
            get { return collection.Count; }
        }

        public void CopyTo(DbDataRow[] array, int arrayIndex)
        {
            collection.CopyTo(array, arrayIndex);
        }

        internal DbDataRowCollection(Database db, DataRowCollection drc, DataColumnCollection dcc)
        {
            this.db = db;
            this.columns = new DbDataColumnCollection(db, dcc);
            foreach (DataRow dr in drc)
                collection.Add(new DbDataRow(db, dr, dcc));
        }

        internal void Add(DbDataRow item)
        {
            collection.Add(item);
        }
        public void Clear()
        {
            collection.Clear();
        }
        internal bool Remove(DbDataRow item)
        {
            return collection.Remove(item);
        }

        #region IEnumerable<DbDataRow> Members
        public IEnumerator<DbDataRow> GetEnumerator()
        {
            return collection.GetEnumerator();
        }
        #endregion
        #region IEnumerable Members
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion
    }

    public enum DatabaseType : int
    {
        SQLite = 0,
        MySQL = 1,
        GenericSQL = 2, // including SQLServer
    }

    internal abstract class Database
    {
        private string connectionString;
        private DatabaseType type;

        public string ConnectionString
        {
            get { return connectionString; }
            set { connectionString = value; }
        }
        public DatabaseType Type
        {
            get { return type; }
        }
        public string Name
        {
            get
            {
                IDbConnection conn = OpenConnection(connectionString);
                if (conn == null || conn.State == ConnectionState.Closed || conn.State == ConnectionState.Broken)
                    return "";
                return conn.Database;
            }
        }

        public event UpdateExceptionHandler OnUpdateException;
        internal bool UpdateException(string query, Exception e)
        {
            if (OnUpdateException != null)
                return OnUpdateException(query, e);
            return false;
        }
        
        internal Database(DatabaseType type)
        {
            this.type = type;
            this.connectionString = "";
        }

        public DbDataRow ExecuteSingle(string query)
        {
            string tq = query;
            if (type == DatabaseType.MySQL || type == DatabaseType.SQLite) tq += " LIMIT 1";
            else tq = tq.Replace("SELECT", "SELECT TOP 1");
            DbDataRowCollection rows = ExecuteQuery(tq);
            if (rows != null && rows.Count > 0) return rows[0];
            return null;
        }
        public DbDataRowCollection ExecuteQuery(string query)
        {
            DataSet ds = new DataSet();
            IDbDataAdapter da = GetDataAdapter(query, connectionString);
            da.Fill(ds);
            if (ds.Tables.Count == 0) return null;
            return new DbDataRowCollection(this, ds.Tables[0].Rows, ds.Tables[0].Columns);
        }
        public int ExecuteNonQuery(string query)
        {
            IDbCommand cmd = GetCommand(query, connectionString);
            return cmd.ExecuteNonQuery();
        }
        public DbObject ExecuteScalar(string query)
        {
            IDbCommand cmd = GetCommand(query, connectionString);
            return new DbObject(cmd.ExecuteScalar());
        }

        public abstract bool TableExists(string table);

        protected abstract IDbConnection OpenConnection(string connectionString);
        protected abstract IDbDataAdapter GetDataAdapter(string query, string connectionString);
        protected abstract IDbCommand GetCommand(string query, string connectionString);

        public static Database Create(DatabaseType type, string connectionString)
        {
            Database db = null;
            switch (type)
            {
#if (!WEB)
                case DatabaseType.SQLite: db = new SQLiteDatabase(); break;
                case DatabaseType.MySQL: db = new MySQLDatabase(); break;
#endif
                case DatabaseType.GenericSQL: db = new GenericSQLDatabase(); break;
            }
            if (db != null) db.connectionString = connectionString;
            return db;
        }

        public bool Update(Stream s)
        {
            StreamReader sr = new StreamReader(s);
            string line;
            try
            {
                while ((line = sr.ReadLine()) != null)
                {
                    line = line.Trim();
                    if (line == "") continue;
                    if (line.StartsWith("--")) continue;
                    try { ExecuteNonQuery(line); }
                    catch (Exception e)
                    {
                        if (UpdateException(line, e))
                            return false;
                    }
                }
            }
            catch (Exception e)
            {
                return UpdateException(null, e);   
            }
            return true;
        }
    }
}
